package com.common.orm;

import com.common.orm.sqlparameters.SQLParameterException;




/**
 * @author Dmitry Savchenko
 */
class SqlStringBuilder {

    private ClassData classData;
    private String fullSql = null;
    private String declarationSql = null;
    private String conditionSql = null;
    private String ending = "";
    private String[] processedFields = null;

    public SqlStringBuilder(ClassData classData, String sql) {
        this.classData = classData;
        this.fullSql = sql;
    }

    public SqlStringBuilder(ClassData classData) {
        this.classData = classData;
    }

    public SqlStringBuilder setFullSqlRequest(String sql) {
        this.fullSql = sql;
        return this;
    }

    public SqlStringBuilder setProcessedFields(String[] fields) {
        this.processedFields = fields;
        return this;
    }
    public String getConditionSQL(){
        return conditionSql;
    }
    
    public String[] getProcessedFields() {
        return processedFields;
    }

    /**
     * Add some sql expression at the end of the sql string</br>
     * Such as ORDER BY, LIMIT 100
     * @return 
     */
    public SqlStringBuilder setEndSqlString(String sql) {
        this.ending = sql;
        return this;
    }

    public SqlStringBuilder setDeclarationSqlPart(String declarationSql) {
        this.declarationSql = declarationSql;
        return this;
    }

    public SqlStringBuilder setConditionSqlPart(String conditionSql) {
        this.conditionSql = conditionSql;
        return this;
    }

    public String formatDeleteSql(ClassData classData) throws SQLParameterException{
        StringBuilder sb=new StringBuilder(100);
        sb.append("DELETE FROM ");
        sb.append(classData.getTableName());
        sb.append(" WHERE ");
        if(conditionSql!=null && !conditionSql.isEmpty()){
            sb.append(conditionSql);
        }else{
            if(classData.getPrimaryKey()==null){
                throw new SQLParameterException("Cannot delete object of class ["+classData.getClazz().getName()+"] because you did not provide primary key. Also you can set conditional sql to select row for deletion");
            }
            String primaryKeyName=classData.getPrimaryKey().getColumn();
            sb.append(primaryKeyName);
            sb.append("=:");
            sb.append(primaryKeyName);
            sb.append(":");
        }
        return sb.toString();
    }
    
    public String formatInsertSql(ClassData classData, boolean autoInsertPrimaryKey) throws SQLParameterException {
        if (fullSql != null) {
            return fullSql;
        }

        StringBuilder sb = new StringBuilder(100);
        sb.append("INSERT INTO ");
        sb.append(classData.getTableName());
        sb.append(" (");
        sb.append(formatInsertColumnList(classData, autoInsertPrimaryKey));
        sb.append(")\nVALUES\n(");
        sb.append(formatInsertValuesList(classData, autoInsertPrimaryKey));
        sb.append(")");
        return sb.toString();
    }

    private String formatInsertValuesList(ClassData classData, boolean autoInsertPrimaryKey) {
        if (processedFields != null) {
            int count = processedFields.length;
            return formatStringWithQuestions(count);
        } else {
            int count = classData.getColumns().length;
            if (autoInsertPrimaryKey) {
                count--;
            }
            return formatStringWithQuestions(count);
        }
    }

    private String formatStringWithQuestions(int questionSymbolCount) {
        StringBuilder sb = new StringBuilder(100);
        for (int i = 0; i < questionSymbolCount; i++) {
            if (i != 0) {
                sb.append(", ");
            }

            sb.append("?");
        }

        return sb.toString();
    }

    
    
    private String formatInsertColumnList(ClassData classData, boolean autoInsertPrimaryKey) throws SQLParameterException {
        StringBuilder sb = new StringBuilder(100);
        boolean first = true;
        if (processedFields != null) {
            if (autoInsertPrimaryKey) {
                for (String field : processedFields) {
                    MethodColumnPair mcp = classData.getColumnsMap().get(field);
                    if (mcp == null) {
                        throw new SQLParameterException("You have provided unknown field [" + field + "]");
                    }
                    //we should not add primary key to the insert list
                    if (mcp.isPrimaryKey()) {
                        continue;
                    }

                    if (first) {
                        first = false;
                    } else {
                        sb.append(", ");
                    }

                    sb.append(field);
                }
            } else {
                for (String field : processedFields) {
                    MethodColumnPair mcp = classData.getColumnsMap().get(field);
//                    if (mcp == null) {
//                        throw new SQLParameterException("You have provided unknown field [" + field + "]");
//                    }

                    if (first) {
                        first = false;
                    } else {
                        sb.append(", ");
                    }

                    sb.append(field);
                }
            }
            return sb.toString();
        }

        if (autoInsertPrimaryKey) {
            for (MethodColumnPair mcp : classData.getColumns()) {
                if (mcp.isPrimaryKey()) {
                    continue;
                }

                if (first) {
                    first = false;
                } else {
                    sb.append(", ");
                }

                sb.append(mcp.getColumn());
            }
        } else {
            for (MethodColumnPair mcp : classData.getColumns()) {
                if (first) {
                    first = false;
                } else {
                    sb.append(", ");
                }

                sb.append(mcp.getColumn());
            }
        }
        return sb.toString();
    }

    /**
     * @param classData
     * information about the class
     * @param usePrimaryKey 
     * use primary key to identify record i.e UPDATE MYTABLE SET ... WHERE PRIM_KEY=:VALUE:
     * @param updatePrimary 
     * set the primary key into the SET section of the update sql
     * @return 
     * sql string
     */
    public String formatUpdateSql(ClassData classData, boolean usePrimaryKey, boolean updatePrimary) throws SQLParameterException {
        if (fullSql != null) {
            return fullSql;
        }
        StringBuilder sb = new StringBuilder(100);
        sb.append("UPDATE ");
        sb.append(getTableName(classData));
        sb.append(" SET ");
        sb.append(formatSetColumnString(classData, updatePrimary));
        sb.append(" \nWHERE ");
        if (conditionSql != null) {
            sb.append(conditionSql);
        } else {
            if (classData.getPrimaryKey() == null) {
                throw new SQLParameterException("Table [" + classData.getTableName() + "] does not have primary key");
            }
            sb.append(classData.getPrimaryKey().getColumn());
            sb.append("=");
            sb.append(":");
            sb.append(classData.getPrimaryKey().getColumn());
            sb.append(":");
        }

        return sb.toString();
    }

    /**
     * @param classData
     * @param updatePrimary
     * if necessary to update primary key in the table<br/>
     * if user has provided with Processed Field, this option will be ignored
     * 
     * @return 
     */
    private String formatSetColumnString(ClassData classData, boolean updatePrimary) throws SQLParameterException {
        StringBuilder sb = new StringBuilder(100);
        boolean firstFlag = true;
        if (processedFields != null) {
            for (String field : processedFields) {
                if (firstFlag) {
                    firstFlag = false;
                } else {
                    sb.append(", ");
                }
                sb.append(field);
                sb.append("=");
                MethodColumnPair pair = classData.getColumnsMap().get(field);
                if (pair == null) {
                    throw new SQLParameterException("Field [" + field + "] does not exist or does not mapped in the table [" + getTableName(classData) + "]");
                }

                sb.append(":");
                sb.append(field);
                sb.append(":");


            }
        } else {
            for (MethodColumnPair mcp : classData.getColumns()) {
                if (mcp.isPrimaryKey()) {
                    continue;
                }
                if (firstFlag) {
                    firstFlag = false;
                } else {
                    sb.append(", ");
                }
                sb.append(mcp.getColumn());
                sb.append("=:");
                sb.append(mcp.getColumn());
                sb.append(":");
            }
        }


        return sb.toString();
    }

    public String formatGetSql(ClassData classData) {
        if (fullSql != null) {
            return fullSql;
        }

        StringBuilder sql = new StringBuilder(100);
        sql.append("SELECT ");
        if (declarationSql != null) {
            sql.append(declarationSql);
        } else {
            sql.append(formatListOfColumn(classData));
        }

        sql.append(" \nFROM ");
        sql.append(getTableName(classData));
        if (conditionSql != null) {
            sql.append(" \nWHERE ");
            sql.append(conditionSql);
        }

        if (ending != null) {
            sql.append(" ");
            sql.append(ending);
        }

        return sql.toString();
    }

    public String formatGetSql() {
        return formatGetSql(classData);
    }

    private String getTableName(ClassData classData) {
        return classData.getTableName();
    }

    private String formatListOfColumn(ClassData classData) {
        StringBuilder list = new StringBuilder(100);
        list.append(OrmUtils.concateArray(classData.getColumns(), new OrmUtils.ArrayConcator() {

            @Override
            public String getValue(Object object) {
                return ((MethodColumnPair) object).getColumn();
            }
        }));

        return list.toString();
    }
}
